use crate::courier::codec::FixedSizedPrefix;

pub(crate) type ClientLocalReqId = u32;
pub(crate) const REQ_ID_SIZE: usize = std::mem::size_of::<ClientLocalReqId>();

#[derive(Debug)]
pub(crate) struct FramePrefix<T> {
    pub(crate) id: ClientLocalReqId,
    pub(crate) typ: T,
    carries_phoenix_feather: bool,
}

#[derive(Debug)]
#[repr(u8)]
pub(crate) enum ReqType {
    Call = 0,
    Cancel = 1,
}
impl TryFrom<u8> for ReqType {
    type Error = u8;
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(ReqType::Call),
            1 => Ok(ReqType::Cancel),
            it => Err(it),
        }
    }
}
#[derive(Debug)]
#[repr(u8)]
pub(crate) enum RespType {
    Done = 0,
    Panicked = 1,
}
impl TryFrom<u8> for RespType {
    type Error = u8;
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(RespType::Done),
            1 => Ok(RespType::Panicked),
            it => Err(it),
        }
    }
}

pub(crate) type ReqFramePrefix = FramePrefix<ReqType>;
impl ReqFramePrefix {
    pub(crate) fn new_cancel(id: ClientLocalReqId) -> Self {
        Self {
            id,
            typ: ReqType::Cancel,
            carries_phoenix_feather: false,
        }
    }
    pub(crate) fn new_call(id: ClientLocalReqId, carries_phoenix_feather: bool) -> Self {
        Self {
            id,
            carries_phoenix_feather,
            typ: ReqType::Call,
        }
    }
}

pub(crate) type RespFramePrefix = FramePrefix<RespType>;
impl RespFramePrefix {
    pub(crate) fn new_done(id: ClientLocalReqId, carries_phoenix_feather: bool) -> Self {
        Self {
            id,
            carries_phoenix_feather,
            typ: RespType::Done,
        }
    }
    pub(crate) fn new_panicked(id: ClientLocalReqId) -> Self {
        Self {
            id,
            carries_phoenix_feather: false,
            typ: RespType::Panicked,
        }
    }
}

#[derive(Debug)]
pub(crate) enum PrefixCodecErr {
    InvalidType(u8),
}

macro_rules! impl_prefix {
    ($typ: tt, $typ_enum_typ: tt) => {
        // |id|    | s|type|payload|
        // |4B| 3b |1b| 4b |
        impl FixedSizedPrefix for $typ {
            type Err = PrefixCodecErr;

            const SIZE: usize = REQ_ID_SIZE + 1;

            fn write_to(self, buf: &mut [u8]) -> Result<(), Self::Err> {
                assert_eq!(buf.len(), Self::SIZE);
                (&mut buf[..REQ_ID_SIZE]).copy_from_slice(&self.id.to_le_bytes());
                buf[REQ_ID_SIZE] = (self.typ as u8) & 0x0f;
                if self.carries_phoenix_feather {
                    buf[REQ_ID_SIZE] |= 0x10;
                }
                Ok(())
            }

            fn read_from(buf: &[u8]) -> Result<(bool, Self), Self::Err> {
                assert_eq!(buf.len(), Self::SIZE);
                let id = {
                    let mut it = [0u8; REQ_ID_SIZE];
                    it.copy_from_slice(&buf[..REQ_ID_SIZE]);
                    it
                };
                let id = ClientLocalReqId::from_le_bytes(id);
                let typ = $typ_enum_typ::try_from(buf[REQ_ID_SIZE] & 0x0f)
                    .map_err(PrefixCodecErr::InvalidType)?;
                let carries_phoenix_feather = buf[REQ_ID_SIZE] & 0x10 != 0;
                Ok((
                    carries_phoenix_feather,
                    Self {
                        id,
                        typ,
                        carries_phoenix_feather,
                    },
                ))
            }
        }
    };
}

impl_prefix!(ReqFramePrefix, ReqType);
impl_prefix!(RespFramePrefix, RespType);
